/* Copyright Andy Mitchell 2007. No warranties */

function Popuper(gtdinboxPage, el, assocObj){
	this.gtdinboxPage = gtdinboxPage;
	this.doc = gtdinboxPage.doc;
	this.window = gtdinboxPage.window;
	
	this.eventObservers = new EventObservers(this);
	this.eventHandlers = new EventHandlers_Mutator(this);
		
	this.el = el;
	if( assocObj ){
		this.assocObj = assocObj;
		this.assocObj.popup = GIPROTOTYPE.bind(this, this.popup);
		this.assocObj.poppedUp = GIPROTOTYPE.bind(this, this.poppedUp);
		this.assocObj.popupOff = GIPROTOTYPE.bind(this, this.popupOff);
		this.assocObj.positionPopup = GIPROTOTYPE.bind(this, this.positionPopup);
		this.assocObj.addPopupChild = GIPROTOTYPE.bind(this, this.addPopupChild);
		this.assocObj.removePopupChild = GIPROTOTYPE.bind(this, this.removePopupChild);
	}
	
	this.el.style.position = "absolute";
	this.el.style.display = "none";
	this.deadZones = [{el:this.el}];
	this.deadZoneFunctions = [];
}

Popuper.prototype.positionPopup = function(type, options){
	// Align the popup against an element; either right or down.
	// Accept in form [x,y], an event (take pointer position), an element (take x/y)
	
	if( !this.on ) return;
	
	var location = (options && options.location )? options.location : "right";
	var failLocation = (options && options.failLocation )? options.failLocation : "left";
	var margin = (options && (options.margin || options.margin==0) )? options.margin : 0;
	
	var x = 0; var y = 0;
	var fBoundFail = null;
	if( type ){
		if( type.currentTarget || type.target || type.srcElement){	// Event
			GIPROTOTYPE.stop(type);
			x = GIPROTOTYPE.pointerX(type);
			y = GIPROTOTYPE.pointerY(type);
			
			var popupDims = GIPROTOTYPE.getDimensions(this.el);
			switch(location){
				case "left":
					x = x - popupDims.width;
					if( x < 10 ) x = 10;
					break;
				case "bottomLeft":
					x = x - popupDims.width;
					y = y + margin;
					if( x < 10 ) x = 10;
					break;
			}
			
			fBoundFail = GIPROTOTYPE.bind(this, (function(xy){this.positionPopup([xy[0], y], {postFail:true})}));
		}else if( type instanceof Array ){
			x = type[0];
			y = type[1];
			fBoundFail = GIPROTOTYPE.bind(this, (function(xy){this.positionPopup([xy[0], y], {postFail:true})}));
		}else if( type.nodeName ){	// Element
			var el = type;
			
			var borderWidth = el.parentNode? GIPROTOTYPE.getBorderWidth(el.parentNode) : 0;	// get width of el's parent (canvas?) border, which is not included in the cumulativeOffset
			var offset = GIPROTOTYPE.cumulativeOffset(el);
				offset[0] = offset[0] + borderWidth;
				offset[1] = offset[1] + borderWidth;
			var dims = GIPROTOTYPE.getDimensions(el);
			var popupDims = GIPROTOTYPE.getDimensions(this.el);

			switch(location){
				case "right":
					x = offset[0] + dims.width + margin;
					y = offset[1];
					break;
				case "left":
					x = offset[0] - dims.width - margin - popupDims.width;
					y = offset[1];
					break;
				case "bottomRight":
					var oDims = GIPROTOTYPE.getDimensions(this.el);
					x = offset[0] + dims.width - oDims.width;
					y = offset[1] + dims.height + margin;
					break;
				case "bottomLeft":
					var oDims = GIPROTOTYPE.getDimensions(this.el);
					x = offset[0] - dims.width - margin - popupDims.width;
					y = offset[1] + dims.height + margin;
					break;
			}
			fBoundFail = GIPROTOTYPE.bind(this, (function(xy){this.positionPopup(type, {location:failLocation, margin:0, postFail:true})}));
		}
	}
	if(x) this.el.style.left = x + "px";
	if(y) this.el.style.top = y + "px";
	
	if(!(options && options.postFail)){
		var xy = null;
		if( xy = this.boundsCheck([x, y]) ){
			if( xy[0]!=0 && fBoundFail ) fBoundFail(xy);
		}
	}
	
	this.eventHandlers.broadcast( "positionPopup" );
};
Popuper.prototype.boundsCheck = function(offset){
	// See if we're in bounds; and if not, return array detailing the correction
		if( !offset || (offset[0]==0 && offset[1]==0) ){
			var display = this.el.style.display;
			this.el.style.display = "";
			offset = GIPROTOTYPE.cumulativeOffset(this.el);
			this.el.style.display = display;
		}
		var viewport = GIPROTOTYPE.viewport(this.doc, this.window, true);
		var dim = GIPROTOTYPE.getDimensions(this.el);

		var deltaX = 0; var deltaY = 0;
		if( (offset[0] + dim.width) > viewport[0] ){	// Move in
			deltaX = viewport[0] - (offset[0] + dim.width) -20;
		}
		if( (offset[1] + dim.height) > viewport[1] ){	// Move up
			deltaY = viewport[1] - (offset[1] + dim.height) - 20;
		}
		if( deltaX!=0 || deltaY!=0 ){
			return [deltaX?offset[0] + deltaX:0, deltaY?offset[1] + deltaY:0];
		}
};

Popuper.prototype.popup = function(type, options){
	// Show the el at x/y; and activate mouse down listeners
	
	this.on = true;
	
	this.positionPopup(type, options);
	if( this.el.style.display=="none" ){
		this.el.style.display = "";	
		this.el.style.zIndex = 5000;
		this.eventObservers.observe( this.gtdinboxPage.gmailDOM.element("document"), "mousedown", this.windowMouseDown, true );
		this.eventHandlers.broadcast("popup");
	}
};
Popuper.prototype.poppedUp = function(){
	return (this.el.style.display!="none");
};

Popuper.prototype.popupOff = function(){
	// Hide the popup element; and also any children it might have
	if( !this.on ) return;
	this.on = false;
	
	this.eventObservers.stopObserving( this.gtdinboxPage.gmailDOM.element("document"), "mousedown", this.windowMouseDown, true );
	if( this.el.style.display!="none" ){
		this.el.style.display = "none";
		this.eventHandlers.broadcast("popupOff");
	}
	
	// Hide any children (and if that child 
	for( var i = 0; i < this.deadZones.length; i++ ){
		var child = this.deadZones[i];
		if( child.popuper ){	// It has popup, use that
			if( child.popuper.popupOff ) child.popuper.popupOff();
		}else if( child.style ){	// It's an element, just hide it
			child.style.display = "none";
		}
	}
};


Popuper.prototype.addPopupChild = function(child){
	// Popup children will not close this popup if clicked; but will be hidden when this popup is hidden
	if( child.popup ){	// It is a popupable object
		this.deadZones[this.deadZones.length] = {'el':child.el, 'popuper':child};
	}else{	// It's just an element
		this.deadZones[this.deadZones.length] = {'el':child};
	}
};
Popuper.prototype.removePopupChild = function(el){
	for( var i = 0; i < this.deadZones.length; i++ ) if( this.deadZones[i].el==el ) this.deadZones.splice(i, 1);
};
Popuper.prototype.resetDeadZones = function(el){
	this.deadZones.length = 1;
};

Popuper.prototype.windowMouseDown = function(event){
	// Test bounds
	if( !GIPROTOTYPE.within( this.el, GIPROTOTYPE.pointerX(event), GIPROTOTYPE.pointerY(event) ) ){
		if( !this.inPopupDeadzone(GIPROTOTYPE.element(event)) ) this.popupOff();
	}
};
Popuper.prototype.inPopupDeadzone = function(elTop){
	// Return true if el falls in a deadzone (include parents)
	for( var i = 0; i < this.deadZones.length; i++ ){	// Now scan up tree to see if this el is part of deadzone
		var el = elTop;
		while( el.parentNode ){
			if( el==this.deadZones[i].el ) return true;
			el = el.parentNode;
		}
	}
	
	// Now do function tests - that consumers of Popuper can define
	for( var i = 0; i < this.deadZoneFunctions.length; i++ ){
		if( this.deadZoneFunctions[i](elTop) ) return true;
	}
}

Popuper.prototype.addDeadZoneFunction = function(f){
	this.deadZoneFunctions.push(f);
};

Popuper.prototype.destroy = function(){
	this.eventObservers.destroy();
	if( this.assocObj ){
		var ns = ["popup", "popupOff", "positionPopup", "addPopupChild", "removePopupChild"];
		for( var i = 0; i < ns.length; i++ ) if( this.assocObj[ns[i]] ) delete this.assocObj[ns[i]];
	}
};